package com.thomsonreuter.search.compare.output;

import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
import com.thomsonreuter.search.compare.config.PropertyConfig;
import com.thomsonreuter.search.compare.constants.CompareConstants;
import com.thomsonreuter.search.compare.data.Comparison;
import org.skyscreamer.jsonassert.FieldComparisonFailure;
import org.skyscreamer.jsonassert.JSONCompareResult;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class ReportWriter implements AutoCloseable {

  private final BufferedWriter writer;

  private int match;
  private int noMatch;
  private int missing;

  private final Multiset<String> failedFields = HashMultiset.create();

  public ReportWriter() throws IOException  {
    writer = Files.newBufferedWriter(Paths.get("reports/comparison-report.md"), StandardCharsets.UTF_8);

    final String timestamp = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm").format(LocalDateTime.now());
    writer.write("# Index comparison results: " + timestamp);

    writer.newLine();
    writer.newLine();
  }

  public synchronized void report(final String fromIndex, final String toIndex, List<Comparison> comparisionList){

    try {
      Integer sampleSize = Integer.valueOf(PropertyConfig.getInstance().getProperties(CompareConstants.SAMPLE_SIZE));
      writeHeader(fromIndex, toIndex, sampleSize);
      comparisionList.forEach(s -> write(s.getDocumentId(),s.getCompareResult()));
      writeSummary();
    } catch (IOException e) {
      e.printStackTrace();
    }

  }

  public synchronized void write(final String documentId, final JSONCompareResult result) {
    if (result == null) {
      missing++;
      write(documentId, "Document missing");
    } else if (result.failed()) {
      noMatch++;

      write(documentId, escape(result.getMessage()));

      Arrays.asList(result.getFieldFailures(), result.getFieldMissing(), result.getFieldUnexpected()).stream()
              .forEach(list -> {
                list.stream().map(failure -> firstNotNull( failure, FieldComparisonFailure::getField))
                        .filter(Objects::nonNull)
                        .distinct()
                        .forEach(failedFields::add);
              });

    } else {
      match++;
    }
  }

  private String firstNotNull(final FieldComparisonFailure field, final Function<FieldComparisonFailure, Object>... functions) {
    return Arrays.stream(functions)
        .map(function -> function.apply(field))
        .filter(Objects::nonNull)
        .map(s -> String.valueOf(s))
        .filter(s -> !Strings.isNullOrEmpty(s))
        .findFirst()
        .orElse(null);
  }

  public void writeHeader(final String from, final String to, final int size) throws IOException {
    writer.write(String.format("Testing %,d documents between%n", size));
    writer.write(String.format("* %s%n", from));
    writer.write(String.format("* %s%n", to));

    writer.newLine();
    writer.newLine();

    writer.write(String.format("## Document counts%n"));
    writer.newLine();

    writer.newLine();


    writer.write(String.format("## Differences%n"));
    writer.newLine();

    writer.write("| Document ID | Message |");
    writer.newLine();

    writer.write("| --------    | ------- |");
    writer.newLine();
  }

  public synchronized void writeSummary() throws IOException {
    writer.newLine();
    writer.write("## Summary");
    writer.newLine();
    writer.newLine();

    final double total = match + noMatch + missing;

    writer.write(String.format("* %,d documents matched successfully (%,.1f%%)%n", match, 100*match/total));
    writer.write(String.format("* %,d documents matched with differences (%,.1f%%)%n", noMatch, 100*noMatch/total));
    writer.write(String.format("* %,d documents not found in new index (%,.1f%%)%n", missing, 100*missing/total));

    writer.newLine();
    writer.write("## Field level summary");
    writer.newLine();
    writer.newLine();

    for (final Multiset.Entry<String> entry : Multisets.copyHighestCountFirst(failedFields).entrySet()) {
      writer.write(String.format("* %,d documents had issues with %s %n", entry.getCount(), entry.getElement()));
    }
  }

  private String escape(final String message) {
    return message
        .replaceAll(" ; ", "<br>")
            .replaceAll("[\r\n]+", "<br>")
        .replaceAll("\\s", "\u00a0")
            .replaceAll("\\|","&#124;");
  }

  private void write(final String documentId, final String message) {
    try {
      writer.write(String.format("| %s | %s |%n", documentId, message));
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public void close() throws IOException {
    writer.close();
  }
}
